﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.model.Instance;
using l1cs.server.server.types;
using System.Collections.ObjectModel;
using l1cs.server.model.map;
using l1cs.server.serverpackets;
using System.Collections;

namespace l1cs.server.model
{
    public class L1World
    {
        private static Logger _log = Logger.getLogger( typeof( L1World ).FullName );

        /// <summary>全てのプレイヤー(String, L1PcInstance)</summary>
        private Hashtable _allPlayers = Hashtable.Synchronized( new Hashtable() );

        /// <summary>全てのペット(int, L1PetInstance)</summary>
        private Hashtable _allPets = Hashtable.Synchronized( new Hashtable() );

        /// <summary>全てのサモンモンスター(int, L1SummonInstance)</summary>
        private Hashtable _allSummons = Hashtable.Synchronized( new Hashtable() );

        /// <summary>全てのオブジェクト(L1ItemInstance入り、L1Inventoryはなし)(int, L1Object)</summary>
        private Hashtable _allObjects = Hashtable.Synchronized( new Hashtable() );

        /// <summary>マップ毎のオブジェクト(L1Inventory入り、L1ItemInstanceはなし)(int, L1Object)</summary>
        private Hashtable[] _visibleObjects = new Hashtable[MAX_MAP_ID + 1];

        /// <summary>全ての戦争(L1War)</summary>
        private ArrayList _allWars = ArrayList.Synchronized( new ArrayList() );

        /// <summary>全てのクラン(Online/Offlineどちらも)(String, L1Clan)</summary>
        private Hashtable _allClans = Hashtable.Synchronized( new Hashtable() );


        private int _weather = 4;

        private bool _worldChatEnabled = true;

        private bool _processingContributionTotal = false;

        private const int MAX_MAP_ID = 8105;

        private static L1World _instance;

        private L1World()
        {
            for ( int i = 0; i <= MAX_MAP_ID; i++ )
            {
                _visibleObjects[i] = Hashtable.Synchronized( new Hashtable() );
            }
        }

        public static L1World getInstance()
        {
            if ( _instance == null )
            {
                _instance = new L1World();
            }
            return _instance;
        }

        /// <summary>
        /// 全ての状態をクリアする。
        /// デバッグ、テストなどの特殊な目的以外で呼び出してはならない。
        /// </summary>
        public void clear()
        {
            _instance = new L1World();
        }

        /// <summary>
        /// オブジェクトを配列に追加する。
        /// </summary>
        /// <param name="l1object"></param>
        public void storeObject( L1Object l1object )
        {
            if ( l1object == null )
            {
                throw new NullReferenceException();
            }

            _allObjects[l1object.getId()] = l1object;
            if ( l1object is L1PcInstance )
            {
                _allPlayers[( (L1PcInstance)l1object ).getName()] = (L1PcInstance)l1object;
            }
            if ( l1object is L1PetInstance )
            {
                _allPets[l1object.getId()] = (L1PetInstance)l1object;
            }
            if ( l1object is L1SummonInstance )
            {
                _allSummons[l1object.getId()] = (L1SummonInstance)l1object;
            }
        }

        public void removeObject( L1Object l1object )
        {
            if ( l1object == null )
            {
                throw new NullReferenceException();
            }

            _allObjects.Remove( l1object.getId() );
            if ( l1object is L1PcInstance )
            {
                _allPlayers.Remove( ( (L1PcInstance)l1object ).getName() );
            }
            if ( l1object is L1PetInstance )
            {
                _allPets.Remove( l1object.getId() );
            }
            if ( l1object is L1SummonInstance )
            {
                _allSummons.Remove( l1object.getId() );
            }
        }

        public L1Object findObject( int oID )
        {
            L1Object ret = (L1Object)_allObjects[oID];
            return ret;
        }

        // _allObjectsのビュー
        //private Collection<L1Object> _allValues;
        // TODO 優先度9 _allObjectsから_allValueskへ変更不可能な配列にコピーしているが更新がない？
        public ICollection getObject()
        {
            //L1Object[] vs = _allValues;
            //return ( vs != null ) ? vs : ( _allValues = _allObjects.Values );
            // TODO 優先度8 Hashtableのコピー渡しよりいい方法はないか？
            L1Object[] ret = new L1Object[_allObjects.Count];
            _allObjects.Values.CopyTo( ret, 0 );
            return ret;
        }

        public L1GroundInventory getInventory( int x, int y, short map )
        {
            int inventoryKey = ( ( x - 30000 ) * 10000 + ( y - 30000 ) ) * -1; // xyのマイナス値をインベントリキーとして使用

            //Object l1object = _visibleObjects[map][inventoryKey];
            Hashtable mapdata = _visibleObjects[map];
            L1Object l1object = (L1Object)mapdata[inventoryKey];
            if ( l1object == null )
            {
                return new L1GroundInventory( inventoryKey, x, y, map );
            }
            else
            {
                return (L1GroundInventory)l1object;
            }
        }

        public L1GroundInventory getInventory( L1Location loc )
        {
            return getInventory( loc.getX(), loc.getY(), (short)loc.getMap().getId() );
        }

        public void addVisibleObject( L1Object l1object )
        {
            if ( l1object.getMapId() <= MAX_MAP_ID )
            {
                _visibleObjects[l1object.getMapId()][l1object.getId()] = l1object;
            }
        }

        public void removeVisibleObject( L1Object l1object )
        {
            if ( l1object.getMapId() <= MAX_MAP_ID )
            {
                _visibleObjects[l1object.getMapId()].Remove( l1object.getId() );
            }
        }

        public void moveVisibleObject( L1Object l1object, int newMap ) // set_Mapで新しいMapにするまえに呼ぶこと
        {
            if ( l1object.getMapId() != newMap )
            {
                if ( l1object.getMapId() <= MAX_MAP_ID )
                {
                    _visibleObjects[l1object.getMapId()].Remove( l1object.getId() );
                }
                if ( newMap <= MAX_MAP_ID )
                {
                    _visibleObjects[newMap][l1object.getId()] = l1object;
                }
            }
        }

        private Dictionary<int, int> createLineMap( Point src, Point target )
        {
            Dictionary<int, int> lineMap = new Dictionary<int, int>();

            /*
             * http://www2.starcat.ne.jp/~fussy/algo/algo1-1.htmより
             */
            int E;
            int x;
            int y;
            int key;
            int i;
            int x0 = src.getX();
            int y0 = src.getY();
            int x1 = target.getX();
            int y1 = target.getY();
            int sx = ( x1 > x0 ) ? 1 : -1;
            int dx = ( x1 > x0 ) ? x1 - x0 : x0 - x1;
            int sy = ( y1 > y0 ) ? 1 : -1;
            int dy = ( y1 > y0 ) ? y1 - y0 : y0 - y1;

            x = x0;
            y = y0;
            /* 傾きが1以下の場合 */
            if ( dx >= dy )
            {
                E = -dx;
                for ( i = 0; i <= dx; i++ )
                {
                    key = ( x << 16 ) + y;
                    lineMap[key] = key;
                    x += sx;
                    E += 2 * dy;
                    if ( E >= 0 )
                    {
                        y += sy;
                        E -= 2 * dx;
                    }
                }
                /* 傾きが1より大きい場合 */
            }
            else
            {
                E = -dy;
                for ( i = 0; i <= dy; i++ )
                {
                    key = ( x << 16 ) + y;
                    lineMap[key] = key;
                    y += sy;
                    E += 2 * dx;
                    if ( E >= 0 )
                    {
                        x += sx;
                        E -= 2 * dy;
                    }
                }
            }

            return lineMap;
        }

        public List<L1Object> getVisibleLineObjects( L1Object src, L1Object target )
        {
            Dictionary<int, int> lineMap = createLineMap( src.getLocation(), target.getLocation() );

            int map = target.getMapId();
            List<L1Object> result = new List<L1Object>();

            if ( map <= MAX_MAP_ID )
            {
                lock ( _visibleObjects )
                {
                    foreach ( L1Object element in _visibleObjects[map].Values )
                    {
                        if ( element.Equals( src ) )
                        {
                            continue;
                        }

                        int key = ( element.getX() << 16 ) + element.getY();
                        if ( lineMap.ContainsKey( key ) )
                        {
                            result.Add( element );
                        }
                    }
                }
            }

            return result;
        }

        public List<L1Object> getVisibleBoxObjects( L1Object l1object, int heading, int width, int height )
        {
            int x = l1object.getX();
            int y = l1object.getY();
            int map = l1object.getMapId();
            L1Location location = l1object.getLocation();
            List<L1Object> result = new List<L1Object>();
            int[] headingRotate = { 6, 7, 0, 1, 2, 3, 4, 5 };
            double cosSita = Math.Cos( headingRotate[heading] * Math.PI / 4 );
            double sinSita = Math.Sin( headingRotate[heading] * Math.PI / 4 );

            if ( map <= MAX_MAP_ID )
            {
                lock ( _visibleObjects )
                {
                    foreach ( L1Object element in _visibleObjects[map].Values )
                    {
                        if ( element.Equals( l1object ) )
                        {
                            continue;
                        }
                        if ( map != element.getMapId() )
                        {
                            continue;
                        }

                        // 同じ座標に重なっている場合は範囲内とする
                        if ( location.isSamePoint( element.getLocation() ) )
                        {
                            result.Add( element );
                            continue;
                        }

                        int distance = location.getTileLineDistance( element
                                .getLocation() );
                        // 直線距離が高さ、幅どちらよりも大きい場合、計算するまでもなく範囲外
                        if ( distance > height && distance > width )
                        {
                            continue;
                        }

                        // objectの位置を原点とするための座標補正
                        int x1 = element.getX() - x;
                        int y1 = element.getY() - y;

                        // Z軸回転させ角度を0度にする。
                        int rotX = (int)Math.Round( x1 * cosSita + y1 * sinSita );
                        int rotY = (int)Math.Round( -x1 * sinSita + y1 * cosSita );

                        int xmin = 0;
                        int xmax = height;
                        int ymin = -width;
                        int ymax = width;

                        // 奥行きが射程とかみ合わないので直線距離で判定するように変更。
                        // if (rotX > xmin && rotX <= xmax && rotY >= ymin && rotY <=
                        // ymax) {
                        if ( rotX > xmin && distance <= xmax && rotY >= ymin
                                && rotY <= ymax )
                        {
                            result.Add( element );
                        }
                    }
                }
            }

            return result;
        }

        public List<L1Object> getVisibleObjects( L1Object l1object )
        {
            return getVisibleObjects( l1object, -1 );
        }

        public List<L1Object> getVisibleObjects( L1Object l1object, int radius )
        {
            L1Map map = l1object.getMap();
            Point pt = l1object.getLocation();
            List<L1Object> result = new List<L1Object>();
            if ( map.getId() <= MAX_MAP_ID )
            {
                lock ( _visibleObjects )
                {
                    foreach ( L1Object element in _visibleObjects[map.getId()].Values )
                    {
                        if ( element.Equals( l1object ) )
                        {
                            continue;
                        }
                        if ( map != element.getMap() )
                        {
                            continue;
                        }

                        if ( radius == -1 )
                        {
                            if ( pt.isInScreen( element.getLocation() ) )
                            {
                                result.Add( element );
                            }
                        }
                        else if ( radius == 0 )
                        {
                            if ( pt.isSamePoint( element.getLocation() ) )
                            {
                                result.Add( element );
                            }
                        }
                        else
                        {
                            if ( pt.getTileLineDistance( element.getLocation() ) <= radius )
                            {
                                result.Add( element );
                            }
                        }
                    }
                }
            }

            return result;
        }

        public List<L1Object> getVisiblePoint( L1Location loc, int radius )
        {
            List<L1Object> result = new List<L1Object>();
            int mapId = loc.getMapId(); // ループ内で呼ぶと重いため

            if ( mapId <= MAX_MAP_ID )
            {
                lock ( _visibleObjects )
                {
                    foreach ( L1Object element in _visibleObjects[mapId].Values )
                    {
                        if ( mapId != element.getMapId() )
                        {
                            continue;
                        }

                        if ( loc.getTileLineDistance( element.getLocation() ) <= radius )
                        {
                            result.Add( element );
                        }
                    }
                }
            }

            return result;
        }

        public List<L1PcInstance> getVisiblePlayer( L1Object l1object )
        {
            return getVisiblePlayer( l1object, -1 );
        }

        public List<L1PcInstance> getVisiblePlayer( L1Object l1object, int radius )
        {
            int map = l1object.getMapId();
            Point pt = l1object.getLocation();
            List<L1PcInstance> result = new List<L1PcInstance>();
            lock ( _allPlayers )
            {
                foreach ( L1PcInstance element in _allPlayers.Values )
                {
                    if ( element.Equals( l1object ) )
                    {
                        continue;
                    }

                    if ( map != element.getMapId() )
                    {
                        continue;
                    }

                    if ( radius == -1 )
                    {
                        if ( pt.isInScreen( element.getLocation() ) )
                        {
                            result.Add( element );
                        }
                    }
                    else if ( radius == 0 )
                    {
                        if ( pt.isSamePoint( element.getLocation() ) )
                        {
                            result.Add( element );
                        }
                    }
                    else
                    {
                        if ( pt.getTileLineDistance( element.getLocation() ) <= radius )
                        {
                            result.Add( element );
                        }
                    }
                }
            }
            return result;
        }

        public List<L1PcInstance> getVisiblePlayerExceptTargetSight( L1Object l1object, L1Object target )
        {
            int map = l1object.getMapId();
            Point objectPt = l1object.getLocation();
            Point targetPt = target.getLocation();
            List<L1PcInstance> result = new List<L1PcInstance>();

            lock ( _allPlayers )
            {
                foreach ( L1PcInstance element in _allPlayers.Values )
                {
                    if ( element.Equals( l1object ) )
                    {
                        continue;
                    }

                    if ( map != element.getMapId() )
                    {
                        continue;
                    }

                    if ( Config.PC_RECOGNIZE_RANGE == -1 )
                    {
                        if ( objectPt.isInScreen( element.getLocation() ) )
                        {
                            if ( !targetPt.isInScreen( element.getLocation() ) )
                            {
                                result.Add( element );
                            }
                        }
                    }
                    else
                    {
                        if ( objectPt.getTileLineDistance( element.getLocation() ) <= Config.PC_RECOGNIZE_RANGE )
                        {
                            if ( targetPt.getTileLineDistance( element.getLocation() ) > Config.PC_RECOGNIZE_RANGE )
                            {
                                result.Add( element );
                            }
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// objectを認識できる範囲にいるプレイヤーを取得する。
        /// </summary>
        /// <param name="l1object"></param>
        /// <returns></returns>
        public List<L1PcInstance> getRecognizePlayer( L1Object l1object )
        {
            return getVisiblePlayer( l1object, Config.PC_RECOGNIZE_RANGE );
        }

        // _allPlayersのビュー
        //private Collection<L1PcInstance> _allPlayerValues;
        //TODO 優先度9 _allPlayersから_allPlayerValuesに変更不可能な配列にコピーしているが更新がまったくない？
        public L1PcInstance[] getAllPlayers()
        {
            //Collection<L1PcInstance> vs = _allPlayerValues;
            //return (vs != null) ? vs : (_allPlayerValues = Collections
            //        .unmodifiableCollection(_allPlayers.Values));
            // TODO 優先度8 Hashtableのコピー渡しよりいい方法はないか？
            L1PcInstance[] ret = new L1PcInstance[_allPlayers.Count];
            _allPlayers.Values.CopyTo( ret, 0 );
            return ret;
        }

        /// <summary>
        /// ワールド内にいる指定された名前のプレイヤーを取得する。
        /// </summary>
        /// <param name="name">プレイヤー名(小文字・大文字は無視される)</param>
        /// <returns>指定された名前のL1PcInstance。該当プレイヤーが存在しない場合はnullを返す。</returns>
        public L1PcInstance getPlayer( String name )
        {
            if ( _allPlayers.ContainsKey( name ) )
            {
                return (L1PcInstance)_allPlayers[name];
            }
            lock ( _allPlayers )
            {
                foreach ( L1PcInstance each in _allPlayers.Values )//getAllPlayers() )
                {
                    if ( string.Compare( each.getName(), name, true ) == 0 )
                    {
                        return each;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// ワールド内のプレイヤー人数を返す。
        /// </summary>
        /// <returns>プレイヤー数</returns>
        public int getAllPlayerCount()
        {
            return _allPlayers.Count;
        }

        //// _allPetsのビュー
        //private Collection<L1PetInstance> _allPetValues;

        //public Collection<L1PetInstance> getAllPets() {
        //    Collection<L1PetInstance> vs = _allPetValues;
        //    return (vs != null) ? vs : (_allPetValues = Collections
        //            .unmodifiableCollection(_allPets.values()));
        //}

        // _allSummonsのビュー
        //private L1SummonInstance[] _allSummonValues;
        // TODO 優先度9 _allSummonsから_allSummonValuesに変更不可能な配列にコピーしているが更新がない？
        //public L1SummonInstance[] getAllSummons()
        //{
        //    L1SummonInstance[] ret = new L1SummonInstance[_allSummons.Count];
        //    _allSummons.Values.CopyTo( ret, 0 );
        //    return ret;
        //}

        /// <summary>
        /// プレイヤーのサモンを返す。
        /// </summary>
        /// <param name="playerId">プレイヤーID</param>
        /// <returns></returns>
        public L1SummonInstance[] getSummons( int playerId )
        {
            List<L1SummonInstance> ret = new List<L1SummonInstance>();
            lock ( _allSummons )
            {
                foreach ( L1SummonInstance lsi in _allSummons.Values )
                {
                    if ( lsi.getId() == playerId )
                    {
                        ret.Add( lsi );
                    }
                }
            }
            return ret.ToArray();
        }

        // TODO 優先度7 Hashtable渡しは絶対ﾀﾞﾒ
        //public Hashtable getAllVisibleObjects()
        //{
        //    return _allObjects;
        //}

        //public Hashtable[] getVisibleObjects()
        //{
        //    return _visibleObjects;
        //}

        public Hashtable getVisibleObjects( int mapId )
        {
            return _visibleObjects[mapId];
        }

        public Object getRegion( Object l1object )
        {
            return null;
        }

        public void addWar( L1War war )
        {
            if ( !_allWars.Contains( war ) )
            {
                _allWars.Add( war );
            }
        }

        public void removeWar( L1War war )
        {
            if ( _allWars.Contains( war ) )
            {
                _allWars.Remove( war );
            }
        }

        // _allWarsのビュー
        //private List<L1War> _allWarList;
        // TODO 優先度9 _allWarListから_allWarListへ変更不可能な配列にコピーしているが更新がない？
        public L1War[] getWarList()
        {
            //List<L1War> vs = _allWarList;
            //return ( vs != null ) ? vs : ( _allWarList = _allWars );
            // TODO 優先度8 Hashtableのコピー渡しよりいい方法はないか？
            L1War[] ret = new L1War[_allWars.Count];
            _allWars.CopyTo( ret, 0 );
            return ret;
        }

        public void storeClan( L1Clan clan )
        {
            L1Clan temp = getClan( clan.getClanName() );
            if ( temp == null )
            {
                _allClans[clan.getClanName()] = clan;
            }
        }

        public void removeClan( L1Clan clan )
        {
            L1Clan temp = getClan( clan.getClanName() );
            if ( temp != null )
            {
                _allClans.Remove( clan.getClanName() );
            }
        }

        public L1Clan getClan( String clan_name )
        {
            L1Clan ret = (L1Clan)_allClans[clan_name];
            return ret;
        }

        // _allClansのビュー
        //private Collection<L1Clan> _allClanValues;
        // TODO 優先度9 _allClansから_allClanValuesへ変更不可能な配列にコピーしているが更新がない？
        public L1Clan[] getAllClans()
        {
            //Collection<L1Clan> vs = _allClanValues;
            //return (vs != null) ? vs : (_allClanValues = Collections.unmodifiableCollection(_allClans.values()));
            // TODO 優先度8 Hashtableのコピー渡しよりいい方法はないか？
            L1Clan[] ret = new L1Clan[_allClans.Count];
            _allClans.Values.CopyTo( ret, 0 );
            return ret;
        }

        public void setWeather( int weather )
        {
            _weather = weather;
        }

        public int getWeather()
        {
            return _weather;
        }

        public void set_worldChatElabled( bool flag )
        {
            _worldChatEnabled = flag;
        }

        public bool isWorldChatElabled()
        {
            return _worldChatEnabled;
        }

        public void setProcessingContributionTotal( bool flag )
        {
            _processingContributionTotal = flag;
        }

        public bool isProcessingContributionTotal()
        {
            return _processingContributionTotal;
        }

        /// <summary>
        /// ワールド上に存在する全てのプレイヤーへパケットを送信する。
        /// </summary>
        /// <param name="packet">送信するパケットを表すServerBasePacketオブジェクト。</param>
        public void broadcastPacketToAll( ServerBasePacket packet )
        {
            //_log.finest( "players to notify : " + getAllPlayers().Count() );
            _log.finest( "players to notify : " + _allPlayers.Count );
            lock ( _allPlayers )
            {
                foreach ( L1PcInstance pc in _allPlayers.Values )//getAllPlayers() )
                {
                    pc.sendPackets( packet );
                }
            }
        }

        /// <summary>
        /// ワールド上に存在する全てのプレイヤーへサーバーメッセージを送信する。
        /// </summary>
        /// <param name="message">送信するメッセージ</param>
        public void broadcastServerMessage( String message )
        {
            broadcastPacketToAll( new S_SystemMessage( message ) );
        }
    }
}
